/*
 * CAudioRender.cpp
 *
 *  Created on: 2017年3月16日
 *      Author: chuanjiang.zh
 */

#include "CAudioRender.h"
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <cstdlib>
#include "TimeHelper.h"
#include "RenderConst.h"
#include "CLog.h"
#include <assert.h>

extern "C"
{
	#include "libavutil/time.h"
}

namespace av
{

static comn::CriticalSection s_cs;


void CAudioRender::sdlAudioCallback(void* userdata, Uint8* stream, int len)
{
	CAudioRender* pThis = (CAudioRender*)userdata;
	pThis->audioCallback(stream, len);
}


CAudioRender::CAudioRender():
		m_audioDev(),
		m_audioSpec(),
		m_curFrame(),
		m_framePos(),
		m_playing(false),
		m_swrContext(),
		m_inFormat(),
		m_outFormat(),
		m_volume(SDL_MIX_MAXVOLUME),
		m_muted(false),
		m_scale(av_make_q(1,1)),
		m_resampleRate()
{
}

CAudioRender::~CAudioRender()
{
	close();
}

int CAudioRender::open(int channels, int sampleRate, int sampleFmt)
{
	int frameSize = 1024;

	SDL_AudioSpec want;
	SDL_zero(want);
	want.channels = channels;
	want.freq = sampleRate;

	m_inFormat = (AVSampleFormat)sampleFmt;
	want.format = chooseSampleFormat(m_inFormat, m_outFormat);

	want.samples = frameSize;
	want.callback = sdlAudioCallback;
	want.userdata = this;

	comn::AutoCritSec lock(s_cs);
	m_audioDev = SDL_OpenAudioDevice(NULL, 0, &want, &m_audioSpec, 0);
	if (m_audioDev == 0)
	{
#ifdef WIN32
		CoInitialize(NULL);
#endif //
		m_audioDev = SDL_OpenAudioDevice(NULL, 0, &want, &m_audioSpec, 0);
		if (m_audioDev == 0)
		{
			const char* err = SDL_GetError();
			CLog::error("CAudioRender(%p) failed to open SDL device. %s\n", this, err);
		}
	}

	//SDL_PauseAudioDevice(m_audioDev, TRUE);

	return 0;
}

void CAudioRender::close()
{
	if (!isOpen())
	{
		return;
	}

	pause();

	{
		comn::AutoCritSec lock2(s_cs);
		comn::AutoCritSec lock(m_cs);
		SDL_CloseAudioDevice(m_audioDev);
		m_audioDev = 0;
	}

	m_frameQueue.clear();

	clearFrame();

	if (m_swrContext)
	{
		swr_close(m_swrContext);
		swr_free(&m_swrContext);
		m_swrContext = NULL;
	}

	m_outFormat = AV_SAMPLE_FMT_NONE;

	m_curPoint.reset();
}

bool CAudioRender::isOpen()
{
	return (m_audioDev != 0);
}

int CAudioRender::play()
{
	pauseDevice(false);
	return 0;
}

int CAudioRender::pause()
{
	pauseDevice(true);
	return 0;
}

void CAudioRender::stop()
{
	pauseDevice(true);

	flush();
}

bool CAudioRender::isPaused()
{
	comn::AutoCritSec lock(m_cs);
	return !m_playing;
}

int CAudioRender::write(AVFrame* frame)
{
	if (!frame)
	{
		return EINVAL;
	}

	if (!isOpen())
	{
		int rc = open(frame->sample_rate, frame->channels, frame->format);
		if (rc != 0)
		{
			return rc;
		}
	}

	AVFrame* outFrame = av_frame_clone(frame);
	m_frameQueue.push(outFrame);

	return 0;
}

void CAudioRender::flush()
{
	m_frameQueue.clear();

	clearFrame();

	m_curPoint.reset();
}

/**
 * 获取音量
 * @return
 */
float CAudioRender::getVolume()
{
	float volume = m_volume;
	volume = volume / SDL_MIX_MAXVOLUME;
	return volume;
}

/**
 * 设置音量
 * @param vol   取值范围：[0.0,1.0]
 * @return
 */
int CAudioRender::setVolume(float vol)
{
	int volume = (int)(vol * SDL_MIX_MAXVOLUME);
	if (volume < 0)
	{
		volume = 0;
	}
	else if (volume > SDL_MIX_MAXVOLUME)
	{
		volume = SDL_MIX_MAXVOLUME;
	}

	comn::AutoCritSec lock(m_cs);
	m_volume = volume;

	return 0;
}

bool CAudioRender::isMute()
{
	comn::AutoCritSec lock(m_cs);
	return m_muted;
}

int CAudioRender::setMute(bool muted)
{
	comn::AutoCritSec lock(m_cs);

	m_muted = muted;

	return 0;
}


int CAudioRender::chooseSampleFormat(int fmt, AVSampleFormat& outFmt)
{
	int sdlFormat = AV_SAMPLE_FMT_S16;
	if (fmt == AV_SAMPLE_FMT_U8)
	{
		sdlFormat = AUDIO_U8;
		outFmt = AV_SAMPLE_FMT_U8;
	}
	else if (fmt == AV_SAMPLE_FMT_S16)
	{
		sdlFormat = AUDIO_S16SYS;
		outFmt = AV_SAMPLE_FMT_S16;
	}
	else if (fmt == AV_SAMPLE_FMT_FLT)
	{
		sdlFormat = AUDIO_F32;
		outFmt = AV_SAMPLE_FMT_FLT;
	}
	else if (fmt == AV_SAMPLE_FMT_FLTP)
	{
		sdlFormat = AUDIO_F32;
		outFmt = AV_SAMPLE_FMT_FLT;
	}
	else
	{
		sdlFormat = AUDIO_S16SYS;
		outFmt = AV_SAMPLE_FMT_S16;
	}
	return sdlFormat;
}

void CAudioRender::pauseDevice(bool toPause)
{
	if (m_audioDev == 0)
	{
		return ;
	}

	SDL_PauseAudioDevice(m_audioDev, toPause);

	{
		comn::AutoCritSec lock(m_cs);
		m_playing = !toPause;
		//m_curPoint.reset();
	}
}


void CAudioRender::audioCallback(Uint8* stream, int len)
{
	memset(stream, m_audioSpec.silence, len);

	int vol = m_volume;
    if (m_muted)
    {
        vol = 0;
    }

	Uint8* buffer = stream;
	int size = len;

	int written = 0;
	// mix last frame
	written = mixLastFrame(buffer, size, vol);
	buffer += written;
	size -= written;

	int tryCount = 10;
	while ((size > 0) && m_playing)
	{
		AVFrame* frame = fetchFrame();
		if (!frame)
		{
			tryCount--;
			if (tryCount > 0)
			{
				av_usleep(1000);
				continue;
			}

			break;
		}

        int64_t pts = (frame->pts == AV_NOPTS_VALUE) ? frame->pkt_pts : frame->pts;
		m_curPoint.reset(getClock(), pts);

		bool consumed = false;
		written = mixFrame(frame, buffer, size, vol, consumed);

		buffer += written;
		size -= written;

		if (consumed)
		{
			av_frame_free(&frame);
		}
		else
		{
			saveFrame(frame, written);
			break;
		}
	}
}

int CAudioRender::mixFrame(AVFrame* pFrame, Uint8* buffer, int size, int vol, bool& consumed)
{
    if (size == 0)
    {
        return 0;
    }

    return mixFrame(pFrame, 0, buffer, size, vol, consumed);
}

int CAudioRender::mixFrame(AVFrame* pFrame, int pos, Uint8* buffer, int size, int vol, bool& consumed)
{
    if (!pFrame)
    {
        return 0;
    }

    int frameSize = pFrame->linesize[0];
    int bytes = av_get_bytes_per_sample((AVSampleFormat)pFrame->format);
    //int frameSize = pFrame->channels * pFrame->nb_samples * bytes;

    if (pos >= frameSize)
    {
        consumed = true;
        return 0;
    }

    uint8_t* frameData = pFrame->data[0];
    int length = std::min(frameSize - pos, size);
    SDL_MixAudioFormat(buffer, frameData + pos, m_audioSpec.format, length, vol);

    consumed = ((frameSize - pos) == length);
    return length;
}

int CAudioRender::mixLastFrame(Uint8* buffer, int size, int vol)
{
    comn::AutoCritSec lock(m_cs);
    if (!m_curFrame)
    {
        return 0;
    }

    bool consumed = false;
    int written = mixFrame(m_curFrame, m_framePos, buffer, size, vol, consumed);
    if (consumed)
    {
        clearFrame();
    }
    else
    {
        m_framePos += written;
    }
    return written;
}

void CAudioRender::saveFrame(AVFrame* pFrame, int pos)
{
    clearFrame();

    comn::AutoCritSec lock(m_cs);
    m_curFrame = pFrame;
    m_framePos = pos;
}

void CAudioRender::clearFrame()
{
    comn::AutoCritSec lock(m_cs);

    if (m_curFrame)
    {
        av_frame_free(&m_curFrame);
        m_framePos = 0;
    }
}

int CAudioRender::resample(AVFrame* inFrame, AVFrame* outFrame)
{
    int samples = outFrame->nb_samples;
    int linesize = outFrame->linesize[0];

    if (m_swrContext == NULL)
    {
        m_swrContext = swr_alloc();

        swr_config_frame(m_swrContext, outFrame, inFrame);

        int rc = swr_init(m_swrContext);
        if (rc != 0)
        {
            CLog::error("CAudioRender swr_init failed. rc:%d\n", rc);
        }

		m_resampleRate = outFrame->sample_rate;
    }
    else
    {
		if (m_resampleRate != outFrame->sample_rate)
		{
			swr_config_frame(m_swrContext, outFrame, inFrame);
			m_resampleRate = outFrame->sample_rate;
		}

        assert(samples == outFrame->nb_samples);
    }

    swr_convert_frame(m_swrContext, outFrame, inFrame);

    if (samples != outFrame->nb_samples)
    {
        int bytes = av_get_bytes_per_sample((AVSampleFormat)outFrame->format);
        outFrame->linesize[0] = outFrame->nb_samples * bytes * outFrame->channels;
    }
    return outFrame->nb_samples;
}

int64_t CAudioRender::getClock()
{
	int64_t clk = util::TimeHelper::getClock();
	if ((m_scale.num > 0) && (m_scale.num != m_scale.den))
	{
		clk = av_rescale(clk, m_scale.num, m_scale.den);
	}
	return clk;
}

AVFrame* CAudioRender::fetchFrame()
{
	bool shouldFlush = false;
	AVRational scale = m_scale;

	int outRate = m_audioSpec.freq * scale.den / scale.num;
	if ((outRate != m_resampleRate) && (m_resampleRate > 0))
	{
		shouldFlush = true;
	}

	if (shouldFlush)
	{
		AVFrame* frame = flushResample();
		m_resampleRate = 0;

		if (frame)
		{
			return frame;
		}
	}

	size_t queSize = 0;
	AVFrame* frame = m_frameQueue.pop(queSize);
	if (!frame)
	{
		return frame;
	}

	AVFrame* outFrame = av_frame_alloc();
	av_frame_copy_props(outFrame, frame);

	outFrame->format = m_outFormat;
	outFrame->channels = m_audioSpec.channels;
	outFrame->channel_layout = av_get_default_channel_layout(m_audioSpec.channels);

	outFrame->sample_rate = av_rescale(m_audioSpec.freq, scale.den, scale.num);
	outFrame->nb_samples = av_rescale(frame->nb_samples, scale.den, scale.num);

	av_frame_get_buffer(outFrame, 1);

	resample(frame, outFrame);

	av_frame_free(&frame);

	return outFrame;
}

AVFrame* CAudioRender::flushResample()
{
	if (m_swrContext == NULL)
	{
		return NULL;
	}

	int64_t outSamples = swr_get_delay(m_swrContext, m_resampleRate);
	if (outSamples <= 0)
	{
		return NULL;
	}

	AVFrame* outFrame = av_frame_alloc();

	outFrame->format = m_outFormat;
	outFrame->channels = m_audioSpec.channels;
	outFrame->channel_layout = av_get_default_channel_layout(m_audioSpec.channels);

	outFrame->sample_rate = m_resampleRate;
	outFrame->nb_samples = outSamples;

	av_frame_get_buffer(outFrame, 1);

	swr_convert_frame(m_swrContext, outFrame, NULL);

	if (outSamples != outFrame->nb_samples)
	{
		int bytes = av_get_bytes_per_sample((AVSampleFormat)outFrame->format);
		outFrame->linesize[0] = outFrame->nb_samples * bytes * outFrame->channels;
	}

	return outFrame;
}

} /* namespace av */
